#include "fsnotify_imp.h"
#include <utils.h>



/* If directory depth exceeds this value, recursive function call is applied */
#define DIR_MAX_DEPTH 1024

struct dentry *
dentry_new(const char *name)
{
    struct dentry *d = g_slice_new(struct dentry);
    d->inode = 0;
    d->name = strdup(name);
    d->parent = NULL;
    d->child = NULL;
    watch_desc_init(&d->wd);
    return d;
}

void
dentry_free(struct dentry *d)
{
    //g_debug("dentry_free(): d: %p, d->name: %s, (void *)d->name: %p", d, d->name, d->name);
    free(d->name);
    d->name = NULL;
    if (d->child) {
        g_tree_destroy(d->child);
    }
    g_slice_free(struct dentry, d);
}

void
dentry_add_child(struct dentry *d, struct dentry *child)
{
    if (d->child == NULL) {
        d->child = g_tree_new_full((GCompareDataFunc) strcmp,
                NULL, NULL, (GDestroyNotify) dentry_free);
    }
    child->parent = d;
    g_tree_insert(d->child, child->name, child);
}

void
dentry_move_child(struct dentry *from, struct dentry *to, struct dentry *child)
{
    g_tree_steal(from->child, child->name);
    dentry_add_child(to, child);
}

struct dentry *
dentry_steal_child(struct dentry *d, const char *name)
{
    struct dentry *d_child;

    if (d->child == NULL) {
        return NULL;
    }
    d_child = (struct dentry *) g_tree_lookup(d->child, name);
    if (G_LIKELY(d_child)) {
        g_tree_steal(d->child, name);
    }
    return d_child;
}

struct dentry *
dentry_lookup_subtree(struct dentry *d, const char *path)
{
    struct dentry *node;
    char *p;
    char *buf = strdup(path);
#ifdef OS_WIN
    /* MSVCRT's strtok() is thread-safe */
    for (p = strtok(buf, G_DIR_SEPARATOR_S); p;
            p = strtok(NULL, G_DIR_SEPARATOR_S)) {
#else
    char *saveptr;
    for (p = strtok_r(buf, G_DIR_SEPARATOR_S, &saveptr); p;
            p = strtok_r(NULL, G_DIR_SEPARATOR_S, &saveptr)) {
#endif
        node = dentry_lookup_child(d, p);
        if (node == NULL) {
            return NULL;
        }
        d = node;
    }
    free(buf);
    return node;
}

static void
dentry_get_path0(struct dentry *d, GString *path)
{
    struct dentry *dbuf[DIR_MAX_DEPTH];
    int i;

    dbuf[0] = d;
    i = 1;
    while (1) {
        if (i >= DIR_MAX_DEPTH) {
            if (dbuf[DIR_MAX_DEPTH - 1]->parent) {
                dentry_get_path0(dbuf[DIR_MAX_DEPTH - 1]->parent, path);
            }
            break;
        }

        dbuf[i] = dbuf[i - 1]->parent;
        if (dbuf[i] == NULL) {
            break;
        }
        ++i;
    }

    --i;
    if (dbuf[i]->parent) {
        g_string_append_c(path, G_DIR_SEPARATOR);
    }
    g_string_append(path, dbuf[i]->name);
    for (--i; i >= 0; --i) {
        path_append_component(path, dbuf[i]->name);
    }
}

GString *dentry_get_path(struct dentry *d, GString *path)
{
    if (path == NULL) {
        path = g_string_sized_new(4096);
    } else if (path->len > 0) {
        path->str[0] = '\0';
        path->len = 0;
    }
    dentry_get_path0(d, path);
    return path;
}



struct fsnotify_evt_hdlr {
    fsnotify_evt_hdlr_t func;
    struct list_head list;
};

struct fsnotify fsnotify = {
        FSNOTIFY_DESC_STATIC_INITIALIZER,
        NULL,
        NULL,
        LIST_HEAD_INIT(fsnotify.evt_queue),
        LIST_HEAD_INIT(fsnotify.evt_hdlr_list),
        PTHREAD_MUTEX_INITIALIZER
};

void
dentry_set_inode(struct dentry *d, uint32_t inode)
{
    g_debug("set dentry inode to %d", inode);
    d->inode = inode;
    g_hash_table_insert(fsnotify.inode_map, &d->inode, d);
}

void
fsnotify_inode_map_update(struct dentry *d)
{
    g_hash_table_insert(fsnotify.inode_map, &d->inode, d);
}

struct dentry *
fsnotify_get_root()
{
    return fsnotify.root;
}

struct fsnotify_desc *
fsnotify_get_desc()
{
    return &fsnotify.desc;
}

struct dentry *
fsnotify_indoe_to_dentry(uint32_t inode)
{
    return (struct dentry *) g_hash_table_lookup(fsnotify.inode_map, &inode);
}

static void
fsnotify_start_watch(dentry_builder_t builder)
{
    fsnotify_desc_open(&fsnotify.desc);
    fsnotify.inode_map = g_hash_table_new(g_int_hash, g_int_equal);
    fsnotify.root = builder(&fsnotify.desc);
}

static void
fsnotify_stop_watch()
{
    pthread_mutex_destroy(&fsnotify.evt_hdlr_mutex);
    list_g_slice_free(&fsnotify.evt_hdlr_list, struct fsnotify_evt_hdlr, list);
    list_g_slice_free(&fsnotify.evt_queue, struct fsnotify_event, evt_list);
    g_hash_table_destroy(fsnotify.inode_map);
    fsnotify.inode_map = NULL;
    dentry_free(fsnotify.root);
    fsnotify.root = NULL;
    fsnotify_desc_close(&fsnotify.desc);
}

static inline void
fsnotify_dispatch_event(struct fsnotify_event *evt)
{
    struct fsnotify_evt_hdlr *ent;
    struct list_head *head = &fsnotify.evt_hdlr_list;

    pthread_mutex_lock(&fsnotify.evt_hdlr_mutex);
    list_for_each_entry(ent, head, list) {
        ent->func(evt);
    }
    pthread_mutex_unlock(&fsnotify.evt_hdlr_mutex);
}

static void
fsnotify_process_event()
{
    struct fsnotify_event *evt;
    struct list_head *pos, *t;
    struct list_head *head = &fsnotify.evt_queue;

    list_for_each_safe(pos, t, head) {
        evt = list_entry(pos, struct fsnotify_event, evt_list);

        fsnotify_dispatch_event(evt);

        list_del(pos);

        fsnotify_event_free(evt);
    }
}


static void *
fsnotify_event_thread(void *noarg)
{
    int ret;
    while (1) {
        ret = fsnotify_read_event(&fsnotify.desc, &fsnotify.evt_queue);
        if (ret < 0) {
            continue;
        }
#ifdef OS_WIN
        if (ret > 0) {
            break;
        }
#else
        pthread_testcancel();
#endif
        fsnotify_process_event();
    }
    return NULL;
}

void
fsnotify_init(dentry_builder_t builder)
{
    fsnotify_start_watch(builder);
    if (pthread_create(&fsnotify.tid, NULL, fsnotify_event_thread, NULL)) {
        g_critical("can't start fsnotify monitor");
        return;
    }
}


/* For testing */
static void
fsnotify_start_watch_with_path(const char *path)
{
    fsnotify_desc_open(&fsnotify.desc);
    fsnotify.inode_map = g_hash_table_new(g_int_hash, g_int_equal);
    fsnotify.root = fsnotify_wach_dir_recursively(&fsnotify.desc, path);
}

/* For testing */
void
fsnotify_init_with_path(const char *path)
{
    fsnotify_start_watch_with_path(path);
    if (pthread_create(&fsnotify.tid, NULL, fsnotify_event_thread, NULL)) {
        g_critical("can't start fsnotify monitor");
        return;
    }
}

void
fsnotify_end()
{
#ifdef OS_WIN
    fsnotify_cancel_read_event();
#else
    pthread_cancel(fsnotify.tid);
#endif
    pthread_join(fsnotify.tid, NULL);
    fsnotify_stop_watch(&fsnotify);
}

void
fsnotify_register_handler(fsnotify_evt_hdlr_t func)
{
    struct fsnotify_evt_hdlr *hdlr = g_slice_new(struct fsnotify_evt_hdlr);
    hdlr->func = func;
    pthread_mutex_lock(&fsnotify.evt_hdlr_mutex);
    list_add_tail(&hdlr->list, &fsnotify.evt_hdlr_list);
    pthread_mutex_unlock(&fsnotify.evt_hdlr_mutex);
}

void
fsnotify_unregister_handler(fsnotify_evt_hdlr_t func)
{
    struct fsnotify_evt_hdlr *hdlr;
    struct list_head *pos, *t;
    struct list_head *head = &fsnotify.evt_hdlr_list;

    pthread_mutex_lock(&fsnotify.evt_hdlr_mutex);
    list_for_each_safe(pos, t, head) {
        hdlr = list_entry(pos, struct fsnotify_evt_hdlr, list);
        if (hdlr->func == func) {
            list_del(pos);
            break;
        }
    }
    pthread_mutex_unlock(&fsnotify.evt_hdlr_mutex);
    g_slice_free(struct fsnotify_evt_hdlr, hdlr);
}

static gboolean
fsnotify_print_dentry_tree0(gpointer unused_key, struct dentry *d,
        GString *path)
{
    gsize end = path->len;
    if (d->parent == NULL) {
        g_string_append(path, d->name);
    } else {
        path_append_component(path, d->name);
    }
    g_message("%s", path->str);
    if (d->child) {
        g_tree_foreach(d->child, (GTraverseFunc) fsnotify_print_dentry_tree0,
                path);
    }
    g_string_truncate(path, end);
    return FALSE;
}

/* For testing */
void
fsnotify_print_dentry_tree()
{
    GString *path_buf = g_string_sized_new(4096);
    fsnotify_print_dentry_tree0(NULL, fsnotify.root, path_buf);
    g_string_free(path_buf, TRUE);
}
